use core::mem::MaybeUninit;
use core::ops::{Deref, DerefMut};
use core::ptr::{copy_nonoverlapping, read_volatile, write_volatile};
use core::slice;
use stm32f1::stm32f103::flash;

use crate::calc_md5;

pub struct FLASH;

impl Deref for FLASH {
    type Target = flash::RegisterBlock;
    fn deref(&self) -> &Self::Target {
        unsafe { &*stm32f1::stm32f103::FLASH::PTR }
    }
}

pub const BASE_ADDR: u32 = 0x08000000;

#[cfg(feature = "sector_1k")]
pub const SECTOR_SIZE: usize = 1 * 1024;

#[cfg(feature = "sector_2k")]
pub const SECTOR_SIZE: usize = 2 * 1024;

const FLASH_KEY1: u32 = 0x45670123;
const FLASH_KEY2: u32 = 0xCDEF89AB;

#[derive(Debug)]
pub enum Error {
    PG,
    WRPRT,
    WRITE,
    MD5
}

fn unlock() {
    if FLASH.cr.read().lock().bit_is_set() {
        FLASH.keyr.write(|w| w.key().variant(FLASH_KEY1) );
        FLASH.keyr.write(|w| w.key().variant(FLASH_KEY2) );
    }   
}

fn wait_done() -> Result<(), Error> {

    loop {
        let temp = FLASH.sr.read();
        if temp.pgerr().bit_is_set() {
            return Err(Error::PG);
        }
        if temp.wrprterr().bit_is_set() {
            return Err(Error::WRPRT);
        }
        if temp.bsy().bit_is_clear() {
            break;
        }
    }

    Ok(())
}

struct WriteGuard {}

impl Drop for WriteGuard {

    fn drop(&mut self) {
        FLASH.cr.modify(|_, w| w.pg().clear_bit());
    }
}

fn start_write() -> WriteGuard {
    FLASH.cr.modify(|_, w| w.pg().set_bit());
    WriteGuard {}
}

// buf.len align 2
pub fn write(addr: u32, buf: &[u8]) -> Result<(), Error> {
    
    let mut index = 0;
    let p = buf.as_ptr();
    wait_done()?;

    unlock();
    let _pg = start_write();

    while index < buf.len() {

        unsafe {
            let target = addr + (index as u32);

            let value = ((p.add(index + 1).read() as u16) << 8) +   
                 (p.add(index).read() as u16);

            write_volatile(target as *mut u16, value);   

            wait_done()?;

            if value != read_volatile(target as *mut u16) {
                return Err(Error::WRITE);
            }
        }     
        
        index += 2;
    }

    Ok(())
}

struct EraseGuard {}

impl Drop for EraseGuard {

    fn drop(&mut self) {
        FLASH.cr.modify(|_, w| w.per().clear_bit());
    }
}

fn start_erase() -> EraseGuard {
    FLASH.cr.modify(|_, w| w.per().set_bit());
    EraseGuard {  }
}

fn erase_impl(addr: u32) -> Result<(), Error> {
    wait_done()?;

    let _p = start_erase();
    FLASH.ar.write(|w| w.far().variant(addr));
    FLASH.cr.modify(|_, w| w.strt().set_bit());
    wait_done()?;

    Ok(())
}

pub fn erase(addr: u32, size: usize) -> Result<(), Error> {
    let mut index = 0;
    unlock();
    while index < size {
        erase_impl(addr + (index as u32))?;
        index += SECTOR_SIZE;
    }
    Ok(())
}

pub fn read(addr: u32, buf: &mut[u8]) {
    unsafe {
        copy_nonoverlapping(addr as *const u8, buf.as_mut_ptr(), buf.len());
    }
}

pub trait Reset {
    
    fn reset(&mut self);
}

pub struct Store<T, const ADDR: u32> {
    value: MaybeUninit<T>,
    check_sum: MaybeUninit<[u8; 16]>,
}

impl <T: Copy + Reset, const ADDR: u32> Store<T, ADDR> {

    pub const fn default() -> Self {
        assert!((size_of::<T>() % 2 == 0), "align 2");
        assert!((size_of::<Self>() % 2 == 0), "align 2");
        Self { value: MaybeUninit::uninit(), check_sum: MaybeUninit::uninit() }
    }

    pub fn load(&mut self) -> Result<(), Error> {
        let self_slice = unsafe {
            slice::from_raw_parts_mut(
                self as *mut Self as *mut u8, 
                size_of::<Self>()
            )
        };

        read(ADDR, self_slice);

        let value_slice = unsafe {
            slice::from_raw_parts(self.value.as_ptr() as *const u8, size_of::<T>())
        };

        let md5 = calc_md5(value_slice);

        let self_md5 = unsafe {
            self.check_sum.assume_init_ref()
        };
        if md5 != *self_md5 {
            self.reset();
            return Err(Error::MD5);
        }
        Ok(())
    }

    pub fn save(&mut self) -> Result<(), Error> {
        let value_slice = unsafe {
            slice::from_raw_parts(self.value.as_ptr() as *const u8, size_of::<T>())
        };

        let md5 = calc_md5(value_slice);

        self.check_sum.write( md5 );

        erase(ADDR, size_of::<Self>())?;

        let self_buf = unsafe {
            slice::from_raw_parts(self as *const Self as *const u8, size_of::<Self>())
        };

        write(ADDR, self_buf)?;
        Ok(())
    }
}

impl<T: Copy + Reset, const ADDR: u32> Deref for Store<T, ADDR> {

    type Target = T;

    fn deref(&self) -> &Self::Target {
        unsafe { self.value.assume_init_ref() }
    }
}

impl<T: Copy + Reset, const ADDR: u32> DerefMut for Store<T, ADDR> {

    fn deref_mut(&mut self) -> &mut Self::Target {
        unsafe { self.value.assume_init_mut() }
    }
}
